package ch05;

import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
 * 实现对 JDBC 的简单封装
 * - 后期模拟实现 Apache DbUtils 的预热案例
 * - 泛型、反射、注解知识点的巩固
 *
 * @author yang
 * @date 2024-04-08
 */
public class SqlRunner {
    /**
     * 连接对象
     */
    private Connection connection;

    public SqlRunner(Connection connection) {
        this.connection = connection;
    }

    public SqlRunner() {
    }

    /**
     * 通用的操作方法 - 增、删、改
     *
     * @param sql 增、删、改的 SQL 语句
     * @param params SQL语句需要的具体数据 - 不定长参数本质是一个数组
     * @return 受影响的行数
     */
    public int update(String sql, Object... params){
        int row = 0;
        // 1、获取连接对象
        PreparedStatement ps = null;
        try {
            // 2、预编译 SQL 语句
            ps = connection.prepareStatement(sql);
            // 3、填充占位符 - 循环遍历不定长参数，调用 ps.setObject(下标,值)
            // insert into xxx values (?, ?, ?, ?);
            setParameters(params, ps);
            // 4、执行 SQL 语句，获取受影响行数
            row = ps.executeUpdate();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            // 5、关闭对象
            DbUtil.close(null, ps, connection);
        }
        return row;
    }

    /**
     * 填充占位符
     * @param params 需要输入的参数
     * @param ps 连接对象
     * @throws SQLException
     */
    private static void setParameters(Object[] params, PreparedStatement ps) throws SQLException {
        for (int i = 0; i < params.length; i++){
            //填充占位符 - 下标从 1 开始
            ps.setObject(i + 1, params[i]);
        }
    }

    /**
     * 通过查询操作 - 查询任何数据表，返回 T 类型的 List 集合
     *
     * @param entityClass 实体对象的 Class 对象
     * @param sql 查询 SQL 语句
     * @param params SQL 语句需要的具体数据
     * @return T 类型的 List 集合
     * @param <T> 实体对象的具体类型
     */
    public <T> List<T> query(Class<T> entityClass, String sql, Object... params){
        // 1、获取连接对象、结果集对象
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            // 2、预编译 SQL 语句
            ps = connection.prepareStatement(sql);
            // 3、填充参数
            setParameters(params, ps);
            // 4、执行查询操作 - ResultSet 对象
            rs = ps.executeQuery();
            // 5、把 ResultSet 对象中的数据封装到 List<T> 集合返回 - 反射、内省、注解
            return beanListHandler(rs, entityClass);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            // 6、关闭对象
            DbUtil.close(rs, ps, connection);
        }
    }

    /**
     * 循环遍历 ResultSet 读到各列各行的数据
     * 先封装到实体对象中
     * 然后把实体对象添加到 List 集合返回
     *
     * @param rs 结果集对象
     * @param entityClass 实体对象的 Class 对象
     * @return List<T>
     * @param <T> 具体的实体对象类型
     */
    private <T> List<T> beanListHandler(ResultSet rs, Class<T> entityClass) {
        // 1、初始化 list 集合
        List<T> list = new ArrayList<>();
            try {
                // 2、获取结果集元数据对象
                ResultSetMetaData metaData = rs.getMetaData();

                // 3、获取 select 语句后面查询的字段数量 - 循环读取各列数据
                int count = metaData.getColumnCount();
                System.out.println("查询列数：" + count);

                // 4、循环遍历结果集 - 循环各行
                while (rs.next()) {
                    // 5、实例化实体对象
                    T entity = entityClass.getConstructor().newInstance();

                    // 6、循环查询各列
                    for (int i = 0; i < count; i++) {
                        // 7、获取 select 语句后面查询的列名称
                        // 可能存在列名称与实体对象的属性名称不一致 - 通过 Column 注解进行映射 - 解析注解
                        String columnName = metaData.getColumnLabel(i + 1);

                        // 8、解析注解 - 根据注解中数据表字段的名称（sta_pos），获取对应实体对象的属性的名称(staPos)
                        String filedName = getFieldName(metaData.getColumnLabel(i + 1), entityClass);

                        // 9、获取各列数据
                        Object columnData = rs.getObject(i + 1);

                        // 10、获取实体对象对应的属性的 Field 对象
                        Field field = entityClass.getDeclaredField(filedName);

                        // 11、设置访问权限
                        field.setAccessible(true);

                        // 12、将各列数据封装到实体对象中
                        field.set(entity, columnData);
                    }
                    // 13、将实体对象添加到 List 集合中
                    list.add(entity);
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            // 14、返回 List 集合
            return list;
    }

    /**
     * 根据数据表字段名称，获取对应实体类对象的属性名称
     * 如：sta_pos -> staPos
     *
     * @param columnLabel 数据表字段名称
     * @param entityClass 实体类对象的 Class 对象
     * @return 实体类对象的属性名称
     * @param <T> T 类型的实体类对象
     */
    private <T> String getFieldName(String columnLabel, Class<T> entityClass){
        Field[] fields = entityClass.getDeclaredFields();
        for (Field field:fields) {
            if (field.isAnnotationPresent(Column.class)) {
                String value = field.getAnnotation(Column.class).value();
                if (value != null && value.equals(columnLabel)){
                    return field.getName();
                }
            }
        }
        return columnLabel;
    }
}
